﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using PacManLib;

namespace PacManAI_Hunters
{
    /// <summary>
    /// 1. This AI heads towards the last known good location of Pac-Man.
    /// 2. If the ghost gets to this location without having caught Pac-Man
    ///    it then starts to move randomly until it sees Pac-Man again.
    /// 3. Even when heading towards Pac-Man, there is a chance that
    ///    the ghost will go off in a random direction.
    /// </summary>
    public class Ghosts : IGhostAI
    {
        #region IGhostAI Members

        /// <summary>
        /// Returns the nickname for this AI.
        /// </summary>
        public string getAIName()
        {
            return "Hunter";
        }

        /// <summary>
        /// Returns the coder's name.
        /// </summary>
        public string getCodersName()
        {
            return "Richard Shepherd";
        }

        /// <summary>
        /// Returns the direction for the ghost.
        /// </summary>
        public PlayerInfo.Direction getDesiredDirection(Board board, PlayerInfo ghostInfo)
        {
            // Can we see Pac-Man?
            if (board.PacMan.Position != Board.UnknownPacmanPosition)
            {
                m_pacManPosition = board.PacMan.Position;
                m_moveRandomly = false;
            }

            // Are we at the Pac-Man position?
            if (m_pacManPosition != null
               &&
               m_pacManPosition == ghostInfo.Position)
            {
                // We are at the last-know Pac-Man position, so we move randomly
                // until we sight him again...
                m_moveRandomly = true;
            }

            if (m_moveRandomly 
                || 
                m_pacManPosition == null
                ||
                m_rnd.Next() < 0.2)
            {
                return (PlayerInfo.Direction)m_rnd.Next(0, 4);
            }
            else
            {
                // We target the Pac-Man...
                return getDirectionToPoint(board, ghostInfo.Position, m_pacManPosition);
            }
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Returns the direction for Pac-Man to move to travel in the direction
        /// of the target.
        /// </summary>
        private PlayerInfo.Direction getDirectionToPoint(Board board, Point ghostPosition, Point target)
        {
            List<MoveInfo> moveInfos = new List<MoveInfo>();
            checkMove(board, ghostPosition, target, PlayerInfo.Direction.Up, moveInfos);
            checkMove(board, ghostPosition, target, PlayerInfo.Direction.Down, moveInfos);
            checkMove(board, ghostPosition, target, PlayerInfo.Direction.Left, moveInfos);
            checkMove(board, ghostPosition, target, PlayerInfo.Direction.Right, moveInfos);

            // We find the direction that has the shortest distance to the
            // target, provided it is valid...
            PlayerInfo.Direction direction = PlayerInfo.Direction.NoChange;
            foreach (MoveInfo moveInfo in moveInfos.OrderBy(x => x.Distance))
            {
                // We check that the direction is not the opposite direction 
                // to the one we are currently heading...
                if (PlayerInfo.oppositeDirection(moveInfo.Direction) != m_previousDirection)
                {
                    // This direction is not opposite to the way we are heading...
                    direction = moveInfo.Direction;
                    break;
                }
            }

            m_previousDirection = direction;
            return direction;
        }

        /// <summary>
        /// If the move passed in is valid, we work out the distance to the target
        /// and add it to the collection of move-infos passed in.
        /// </summary>
        private void checkMove(Board board, Point ghostPosition, Point target, PlayerInfo.Direction direction, List<MoveInfo> moveInfos)
        {
            int distance = getDistanceToPoint(board, ghostPosition, target, direction);
            if (distance != -1)
            {
                moveInfos.Add(new MoveInfo { Direction = direction, Distance = distance });
            }
        }

        /// <summary>
        /// Returns the distance to the target if Pac-Man moves in the direction
        /// passed in. Returns -1 if the direction would be an illegal move.
        /// </summary>
        private int getDistanceToPoint(Board board, Point ghostPosition, Point target, PlayerInfo.Direction direction)
        {
            Point tile = new Point();
            if (isLegalMove(board, ghostPosition, direction, ref tile))
            {
                return distanceSquared(tile, target);
            }
            else
            {
                return -1;
            }
        }

        /// <summary>
        /// Returns the distance-squared between two points.
        /// </summary>
        private int distanceSquared(Point a, Point b)
        {
            int diffX = b.X - a.X;
            int diffY = b.Y - a.Y;
            return diffX * diffX + diffY * diffY;
        }

        /// <summary>
        /// True if Pac-Man can move in the direction passed in. If so, the tile
        /// is also filled in.
        /// </summary>
        private bool isLegalMove(Board board, Point ghostPosition, PlayerInfo.Direction direction, ref Point tile)
        {
            tile = ghostPosition;
            switch (direction)
            {
                case PlayerInfo.Direction.Up:
                    tile.Y--;
                    break;
                case PlayerInfo.Direction.Down:
                    tile.Y++;
                    break;
                case PlayerInfo.Direction.Left:
                    tile.X--;
                    break;
                case PlayerInfo.Direction.Right:
                    tile.X++;
                    break;
            }

            // Is the tile on the board?
            if (tile.X < 0) return false;
            if (tile.X >= board.Width) return false;
            if (tile.Y < 0) return false;
            if (tile.Y >= board.Height) return false;

            // Is the tile a wall?
            if (board.Items[tile.X, tile.Y] == Board.Item.Wall) return false;

            return true;
        }

        #endregion

        #region Private data

        // Random number generator...
        private static int m_seed = 1;
        private Random m_rnd = new Random(m_seed++);

        // The last-known position of Pac-Man, or null if we
        // don't know Pac-Man's position...
        private Point m_pacManPosition = new Point(14, 23);

        // True if we should be moving randomly, false to 
        // be heading for Pac-Man's last position...
        private bool m_moveRandomly = false;

        // Info about one direction we can take and the
        // distance (squared) to the target...
        private class MoveInfo
        {
            public PlayerInfo.Direction Direction { get; set; }
            public int Distance { get; set; }
        }

        // The previous direction. For this algorithm, Pac-Man does
        // not reverse direction, except when he's scared. Otherwise he 
        // can end up oscillating between two tiles.
        private PlayerInfo.Direction m_previousDirection = PlayerInfo.Direction.NoChange;

        #endregion

    }
}
